package com.yh.license;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.NetworkInterface;
import java.security.MessageDigest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 硬件信息工具类
 *
 * @author yuhuan
 * @date 2024/11/26
 */
@Slf4j
public class HardwareInfoUtils {

    public static final String DESIGNATED_HARDWARE_PATH = "static/json/designated_hardware.json";

    /**
     * 获取所有 Windows 系统磁盘的序列号
     *
     * @return 包含所有磁盘序列号的列表，如果获取失败则返回空列表
     */
    public static List<String> getAllWindowsDiskSerialNumbers() {
        List<String> serialNumbers = new ArrayList<>();
        try {
            Process process = Runtime.getRuntime().exec("wmic diskdrive get serialnumber");
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;

            reader.readLine();

            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (!line.isEmpty()) {
                    serialNumbers.add(line);
                }
            }
            reader.close();
        } catch (Exception e) {
            log.error("无法获取磁盘序列号: " + e.getMessage());
        }
        return serialNumbers;
    }

    /**
     * 获取所有 Linux 系统磁盘的序列号
     *
     * @return 包含所有磁盘序列号的列表，如果获取失败则返回空列表
     */
    public static List<String> getAllLinuxDiskSerialNumbers() {
        List<String> serialNumbers = new ArrayList<>();
        try {
            String[] cmd = {"/bin/bash", "-c", "lsblk -ndo SERIAL"};
            Process process = Runtime.getRuntime().exec(cmd);
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;

            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (!line.isEmpty()) {
                    serialNumbers.add(line);
                }
            }
            reader.close();

            int exitCode = process.waitFor();
            if (exitCode != 0) {
                log.error("获取磁盘序列号命令执行失败，退出码：" + exitCode);
            }
        } catch (Exception e) {
            log.error("无法获取磁盘序列号: " + e.getMessage());
        }
        return serialNumbers;
    }

    /**
     * 获取所有网络接口的 MAC 地址
     * <p>
     * 系统特定的获取 MAC 地址的命令：
     * - Windows 系统：使用 `wmic nic get MACAddress` 命令来获取 MAC 地址。
     * - Linux 系统：使用 `ip link show` 或 `ifconfig -a` 命令获取网络接口的信息，再提取 MAC 地址。
     * - 例如：`ip link show | grep ether | awk '{print $2}'` 可以用来提取所有的 MAC 地址。
     *
     * @return 包含所有 MAC 地址的列表，如果获取失败则返回空列表
     */
    public static List<String> getAllMacAddresses() {
        List<String> macAddresses = new ArrayList<>();
        try {
            for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
                if (networkInterface.isLoopback() || !networkInterface.isUp()) {
                    continue;
                }

                byte[] macBytes = networkInterface.getHardwareAddress();
                if (macBytes != null) {
                    StringBuilder macAddress = new StringBuilder();
                    for (byte b : macBytes) {
                        macAddress.append(String.format("%02X:", b));
                    }
                    if (macAddress.length() > 0) {
                        macAddress.setLength(macAddress.length() - 1);
                    }
                    macAddresses.add(macAddress.toString());
                }
            }
        } catch (Exception e) {
            log.error("无法获取 MAC 地址: " + e.getMessage());
        }
        return macAddresses;
    }

    /**
     * 获取所有 CPU ID
     *
     * @return 包含所有 CPU ID 的列表，如果获取失败则返回空列表
     */
    public static List<String> getAllCpuIds() {
        List<String> cpuIds = new ArrayList<>();
        try {
            String os = System.getProperty("os.name").toLowerCase();
            String[] command;

            if (os.contains("win")) {
                command = new String[]{"wmic", "cpu", "get", "ProcessorId"};
            } else if (os.contains("nix") || os.contains("nux") || os.contains("mac")) {
                command = new String[]{"/bin/bash", "-c", "dmidecode -t processor | grep 'ID'"};
            } else {
                throw new UnsupportedOperationException("不支持的操作系统: " + os);
            }

            Process process = Runtime.getRuntime().exec(command);
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;

            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (!line.isEmpty() && !line.toLowerCase().contains("processorid")) {
                    cpuIds.add(line);
                }
            }
            reader.close();

            int exitCode = process.waitFor();
            if (exitCode != 0) {
                log.error("获取 CPU ID 命令执行失败，退出码：" + exitCode);
            }
        } catch (Exception e) {
            log.error("无法获取 CPU ID: " + e.getMessage());
        }
        return cpuIds;
    }

    /**
     * 使用硬件特征（如 MAC 地址、磁盘序列号等）生成唯一标识
     *
     * @param hardwareFeatures 包含硬件特征（如 MAC 地址、磁盘序列号）的列表
     * @return 硬件指纹标识（SHA-256 哈希值），如果特征为空则返回 "UNKNOWN"
     */
    public static String generateUniqueIdentifier(List<String> hardwareFeatures) {
        if (hardwareFeatures == null || hardwareFeatures.isEmpty()) {
            return "UNKNOWN";
        }
        Set<String> uniqueFeaturesSet = new HashSet<>(hardwareFeatures);
        List<String> uniqueFeatures = new ArrayList<>(uniqueFeaturesSet);
        Collections.sort(uniqueFeatures);
        String combinedFeatures = String.join("-", uniqueFeatures);
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] hash = md.digest(combinedFeatures.getBytes());
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                hexString.append(String.format("%02x", b));
            }
            return hexString.toString();
        } catch (Exception e) {
            log.error("哈希处理失败: " + e.getMessage());
            return combinedFeatures;
        }
    }

    /**
     * 读取协定的硬件信息配置文件
     *
     * @return 协定的硬件信息对象 {@link HardwareInfoDTO}，如果读取失败则返回空对象
     */
    private static HardwareInfoDTO readHardwareInfo() {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            ClassPathResource resource = new ClassPathResource(DESIGNATED_HARDWARE_PATH);
            JsonNode jsonNode = objectMapper.readTree(resource.getInputStream());
            return objectMapper.treeToValue(jsonNode, HardwareInfoDTO.class);
        } catch (IOException e) {
            log.error("无法读取协定的硬件信息：" + e.getMessage());
            e.printStackTrace();
        }
        return HardwareInfoDTO.empty();
    }

    /**
     * 校验当前设备的 MAC 地址是否包含所有协定配置的 MAC 地址
     *
     * @return true 如果当前设备的 MAC 地址包含所有协定的 MAC 地址，false 否则
     */
    private static boolean checkMac() {
        HardwareInfoDTO hardwareInfoDTO = readHardwareInfo();
        List<String> oldMacAddresses = hardwareInfoDTO.getMacAddresses();
        List<String> allMacAddresses = getAllMacAddresses();

        // 将 MAC 地址统一转换为大写字母
        List<String> upperOldMacAddresses = oldMacAddresses.stream().map(String::toUpperCase).collect(Collectors.toList());
        List<String> upperAllMacAddresses = allMacAddresses.stream().map(String::toUpperCase).collect(Collectors.toList());

        log.debug("当前设备的 MAC 地址：" + upperAllMacAddresses);

        // 检查是否包含
        boolean isContained = upperAllMacAddresses.containsAll(upperOldMacAddresses);
        if (!isContained) {
            log.warn("当前设备的 MAC 地址未包含所有协定的 MAC 地址");
        }
        return isContained;
    }

    /**
     * 校验当前设备的磁盘序列号是否包含所有协定配置的磁盘序列号
     *
     * @return true 如果当前设备的磁盘序列号包含所有协定的磁盘序列号，false 否则
     */
    private static boolean checkDisk() {
        HardwareInfoDTO hardwareInfoDTO = readHardwareInfo();
        List<String> oldDiskSerialNumbers = hardwareInfoDTO.getDiskSerialNumbers();
        List<String> allDiskSerialNumbers = new ArrayList<>();

        String systemName = hardwareInfoDTO.getSystem();
        if ("windows".equalsIgnoreCase(systemName)) {
            allDiskSerialNumbers = getAllWindowsDiskSerialNumbers();
            log.debug("当前设备的磁盘序列号：" + allDiskSerialNumbers);
        } else if ("linux".equalsIgnoreCase(systemName)) {
            allDiskSerialNumbers = getAllLinuxDiskSerialNumbers();
            log.debug("当前设备的磁盘序列号：" + allDiskSerialNumbers);
        }

        // 将磁盘序列号统一转换为大写字母
        List<String> upperOldDiskSerialNumbers = oldDiskSerialNumbers.stream().map(String::toUpperCase).collect(Collectors.toList());
        List<String> upperAllDiskSerialNumbers = allDiskSerialNumbers.stream().map(String::toUpperCase).collect(Collectors.toList());

        // 检查是否包含
        boolean isContained = upperAllDiskSerialNumbers.containsAll(upperOldDiskSerialNumbers);
        if (!isContained) {
            log.warn("当前设备的磁盘序列号未包含所有协定的磁盘序列号");
        }
        return isContained;
    }

    /**
     * 校验当前设备的 CPU ID 是否包含所有协定配置的 CPU ID
     *
     * @return true 如果当前设备的 CPU ID 包含所有协定的 CPU ID，false 否则
     */
    private static boolean checkCpu() {
        HardwareInfoDTO hardwareInfoDTO = readHardwareInfo();
        List<String> oldCpuIds = hardwareInfoDTO.getCpuIds();
        List<String> allCpuIds = getAllCpuIds();

        // 将 CPU ID 转换为大写字母
        List<String> upperOldCpuIds = oldCpuIds.stream().map(String::toUpperCase).collect(Collectors.toList());
        List<String> upperAllCpuIds = allCpuIds.stream().map(String::toUpperCase).collect(Collectors.toList());

        log.debug("当前设备的 CPU ID：" + upperAllCpuIds);

        // 检查是否包含
        boolean isContained = upperAllCpuIds.containsAll(upperOldCpuIds);
        if (!isContained) {
            log.warn("当前设备的 CPU ID 未包含所有协定的 CPU ID");
        }
        return isContained;
    }

    /**
     * 根据指定的验证类型（如磁盘、MAC 地址、CPU ID 或全部）启动硬件特征验证
     *
     * @param vt 验证类型 {@link VerificationType}
     * @return true 如果当前设备的所有硬件特征都通过验证，false 否则
     */
    public static boolean startCheck(VerificationType vt) {
        switch (vt) {
            case DISK:
                return checkDisk();
            case MAC:
                return checkMac();
            case CPU:
                return checkCpu();
            case ALL:
                return checkDisk() && checkMac() && checkCpu();
            default:
                log.warn("未知的验证类型: " + vt);
                return false;
        }
    }
}
